package com.hdl.factory;

import com.hdl.anno.Autowired;
import com.hdl.anno.Repository;
import com.hdl.anno.Service;
import com.hdl.utils.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hedonglin
 * @date 2020/5/3 12:44
 * @description Bean工厂，获取配置文件中定义的bean
 */
public class BeanFactory {
    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
    private static Map<String, Object> map = new HashMap<>();  // 存储对象
    private static List<String> classNames = new ArrayList<>();  // 存储对象


    static {
        try {
            // 1、读取配置文件的包名
//            BeanFactory.class.getClassLoader().getResourceAsStream("");
            // 1、获取要扫描的包下的类集合
            // 获取包名
            doScanner("com.hdl");

            // 2、获取类上面标有注解Service/Repository的类，并放入Map中
            doInstance();

            // 3、遍历带有注解的类，获取具有Autowired注解的字段，为字段使用反射技术赋值
            doAutowired();

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | IOException e) {
            e.printStackTrace();
        }
    }




    private static void doAutowired() throws IllegalAccessException {
        if (map.isEmpty()){
            return;
        }

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            // 获取字节码类
            Object obj = entry.getValue();
            Class<?> clazz = obj.getClass();

            // 获取所有的字段
            for (Field field : clazz.getDeclaredFields()) {
                // 获取字段类型（这里没有ref属性，暂时通过字段类型首字母小写实现）
                String name = StringUtils.toLowerFirstCase(field.getType().getSimpleName());
                // 根据字段名，从Map中获取依赖的属性
                Object o = map.get(name);
                if (o ==null){
                    continue;
                }
                Autowired autowired = field.getDeclaredAnnotation(Autowired.class);
                // 只有有声明字段，并且字段上声明了Autowired注解才进行set方法设置
                if (autowired != null){
                    // 开启暴力访问
                    field.setAccessible(true);
                    // 设置属性
                    field.set(obj, o);
                }

            }
            // 将经过设置属性的实例重新放入Map
            map.put(entry.getKey(), obj);
        }
    }

    /**
     * 初始化
     */
    private static void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (classNames.isEmpty()){
            return;
        }

        for (String className : classNames) {
            // 反射获取字节码文件
            Class<?> clazz = Class.forName(className);

            // 判断该类上是否有Service注解
            if (clazz.isAnnotationPresent(Service.class)){
                // 获取注解
                Service service = clazz.getAnnotation(Service.class);

                // 获取注解上的BeanName
                String beanName = service.value();

                // 注解上的BeanName为空，使用类名首字母小写
                if ("".equals(beanName)){
                    beanName = StringUtils.toLowerFirstCase(clazz.getSimpleName());
                }

                // 实例化类(跟XML一样，解析Bean标签的id和class放入map中，然后再去处理依赖问题)
                Object o = clazz.newInstance();
                map.put(beanName, o);

                // 如果使用的是接口注入
            }else if (clazz.isAnnotationPresent(Repository.class)){
                // 获取注解
                Repository repository = clazz.getAnnotation(Repository.class);

                // 获取注解上的BeanName
                String beanName = repository.value();

                // 注解上的BeanName为空，使用类名首字母小写
                if ("".equals(beanName)){
                    beanName = StringUtils.toLowerFirstCase(clazz.getSimpleName());
                }
                // 实例化类(跟XML一样，解析Bean标签的id和class放入map中，然后再去处理依赖问题)
                Object o = clazz.newInstance();
                map.put(beanName, o);
            }
        }
    }

    /**
     * 扫描指定包下的文件
     * @param scanPackage 包名
     */
    private static void doScanner(String scanPackage) throws IOException {
        // 获取包路径
        URL url = BeanFactory.class.getClassLoader().getResource(scanPackage.replaceAll("\\.", "/"));
        File files = new File(url.getFile());

        for (File file : files.listFiles()) {
            if (file.isDirectory()) {
                //递归查找文件夹【即对应的包】下面的所有文件
                assert !file.getName().contains(".");
                doScanner(scanPackage + '.' + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().substring(0, file.getName().length() - 6);
                classNames.add(className);
            }
        }

    }

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return map.get(id);
    }

}
